/*	$Id: sock.c 1080 2010-04-27 21:33:48Z phrakt $	*/
/*
 * Copyright (c) 2007 Jean-Francois Brousseau <phrakt@gmail.com>
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. The name of the author may not be used to endorse or promote products
 *    derived from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
 * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
 * AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
 * THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL  DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
 * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
 * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
 * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

#include <sys/param.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/stat.h>

#if defined(__linux__)
#include <sys/sendfile.h>
#endif

#include <netinet/in.h>
#include <arpa/inet.h>

#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>

#include <canopy/log.h>
#include <canopy/mem.h>
#include <canopy/thread.h>
#include <canopy/string.h>

#include "msg.h"
#include "http.h"
#include "sock.h"
#include "entity.h"
#include "private.h"



static const char *http_sock_state_names[] = {
	"INITIALIZED",
	"CONNECTED",
	"LISTENING",
	"CLOSING",
	"CLOSED",
};





static http_sock_t* http_sock_alloc       (enum http_sock_type, int);
static void         http_sock_free        (http_sock_t *);
static ssize_t      http_sock_findeoh     (const uint8_t *, size_t, size_t *);
static ssize_t      http_sock_recv_nolock (http_sock_t *, void *, size_t);
static ssize_t      http_sock_send_nolock (http_sock_t *, const void *, size_t);
static int          http_sock_sendfile_nolock (http_sock_t *, int, off_t,
						size_t *, int);
static void         http_sock_setstate    (http_sock_t *, enum http_sock_state);
static int          http_sock_growrbuf    (http_sock_t *, size_t);



/*
 * http_sock_connect()
 *
 * Create a new stream socket and connect it to the remote address <addr>.
 *
 * Returns the socket on success, or -1 on failure.
 */
http_sock_t*
http_sock_connect(http_sockaddr_t *addr)
{
	int s;
	socklen_t len = 0;
	sa_family_t family;
	http_sock_t *sock;

	family = http_addr_getfamily(addr);
	if (family == PF_INET)
		len = sizeof(addr->sa_addr.sa_ipv4);
#if defined(IPV6_SUPPORT)
	else if (family == PF_INET6)
		len = sizeof(addr->sa_addr.sa_ipv6);
#endif
	else {
		http_log_err("unsupported protocol family");
		return (NULL);
	}


	http_log_trace("socket(domain=%d, type=%d, protocol=%d)",
	    family, SOCK_STREAM, 0);
	if ((s = socket(family, SOCK_STREAM, 0)) == -1) {
		http_log_errno("failed to create HTTP socket");
		return (NULL);
	}

	if ((sock = http_sock_alloc(HTTP_SOCK_CONN, s)) == NULL) {
		(void)close(s);
		return (NULL);
	}

	http_log_trace("connect(fd=%d, addr=%p, addrlen=%u)", s, addr, len);
	if (connect(s, (struct sockaddr *)addr, len) == -1) {
		http_log_errno("failed to connect HTTP socket");
		http_sock_free(sock);
		return (NULL);
	}

	http_sock_setstate(sock, HTTP_SOCK_CONNECTED);

	if ((sock->hs_session = http_session_create(sock)) == NULL) {
		http_log_errno("failed to create HTTP session");
		http_sock_free(sock);
		return (NULL);
	}

	return (sock);
}

/*
 * http_sock_bind()
 *
 * Create a new stream socket and bind it to the local socket address <addr>,
 * whose length is specified in <len>.
 *
 * Returns the HTTP socket on success, or NULL on failure.
 */
http_sock_t*
http_sock_bind(struct sockaddr *addr, socklen_t len)
{
	int s, one = 1;
	size_t alen;
	char abuf[48];
	http_sock_t *sock;

	if (addr->sa_family == AF_INET) {
		inet_ntop(addr->sa_family,
		    &((struct sockaddr_in *)addr)->sin_addr, abuf,
		    sizeof(abuf));
		alen = strlen(abuf);
		snprintf(abuf + alen, sizeof(abuf) - alen, ":%u",
		    htons(((struct sockaddr_in *)addr)->sin_port));
	}

	if ((s = socket(addr->sa_family, SOCK_STREAM, 0)) == -1) {
		http_log_errno("failed to create socket");
		return (NULL);
	}

	if (setsockopt(s, SOL_SOCKET, SO_REUSEADDR, &one, sizeof(one)) == -1)
		http_log_warn("failed to make address reusable");

	http_log_trace("bind(fd=%d, addr=%p, addrlen=%u)", s, addr, len);
	if (bind(s, addr, len) == -1) {
		http_log_errno("failed to bind HTTP socket to %s",
		    abuf);
		(void)close(s);
		return (NULL);
	}

	http_log_trace("listen(fd=%d, backlog=%d)", s, 32);
	if (listen(s, 32) == -1) {
		http_log_err("failed to listen on HTTP socket");
		(void)close(s);
		return (NULL);
	}

	if ((sock = http_sock_alloc(HTTP_SOCK_LISTEN, s)) == NULL) {
		http_log_err("failed to allocate HTTP socket");
		(void)close(s);
		return (NULL);
	}

	http_sock_setstate(sock, HTTP_SOCK_LISTENING);

	http_log_info("ready to listen for HTTP requests on %s", abuf);

	return (sock);
}

/*
 * http_sock_accept()
 *
 * Accept incoming HTTP connections from the socket <sock> created with
 * http_sock_bind().  The argument <timeout> can specify an optional timeout
 * value for the call.  If <timeout> is NULL pointer, no timeout is applied.
 *
 * Returns a pointer to a structure representing the socket for this endpoint
 * of the accepted connection on success, or NULL on failure.
 */
http_sock_t*
http_sock_accept(http_sock_t *sock, const struct timeval *timeout)
{
	int ret;
	socklen_t len;
	char abuf[48];
	fd_set fds;
	struct timeval to, *top = NULL;
	http_sock_t *ss = NULL;

	if (timeout != NULL) {
		to.tv_sec = timeout->tv_sec;
		to.tv_usec = timeout->tv_usec;
		top = &to;

		FD_ZERO(&fds);
		FD_SET(sock->hs_fd, &fds);

		http_log_trace("select(max=%d, readfds=%p, "
		    "writefds=%p, exceptfds=%p, timeout=%p)",
		    sock->hs_fd + 1, &fds, (void *)NULL, (void *)NULL, top);

		ret = select(sock->hs_fd + 1, &fds, NULL, NULL, top);
		if (ret == -1) {
			if (errno == EAGAIN)
				return (NULL);
		} else if (ret == 0)
			return (NULL);

		if (!FD_ISSET(sock->hs_fd, &fds))
			return (NULL);
	}

	if ((ss = http_sock_alloc(HTTP_SOCK_CONN, -1)) == NULL)
		return (NULL);

	len = sizeof(ss->hs_raddr);
	http_log_trace("accept(fd=%d, addr=%p, addrlen=%p)",
	    sock->hs_fd, &ss->hs_raddr, &len);
	ss->hs_fd = accept(sock->hs_fd, (struct sockaddr *)&ss->hs_raddr, &len);
	if (ss->hs_fd == -1) {
		http_log_errno("failed to accept");
		http_sock_free(ss);
		return (NULL);
	}

	http_sock_setstate(ss, HTTP_SOCK_CONNECTED);

	if ((ss->hs_session = http_session_create(sock)) == NULL) {
		http_log_errno("failed to create HTTP session");
		http_sock_free(ss);
		return (NULL);
	}

	len = sizeof(ss->hs_laddr);
	ret = getsockname(ss->hs_fd, (struct sockaddr *)&ss->hs_laddr, &len);
	if (ret == -1)
		http_log_errno("failed to get socket local address");

	if (http_addr_ntop(&ss->hs_laddr, abuf, sizeof(abuf)) == NULL) {
		http_log_err("failed to format local address");
		http_sock_free(ss);
		return (NULL);
	}
	else if ((ss->hs_laddr_str = cnp_strdup(abuf)) == NULL) {
		http_log_errno("failed to copy local address");
		http_sock_free(ss);
		return (NULL);
	}

	if (http_addr_ntop(&ss->hs_raddr, abuf, sizeof(abuf)) == NULL) {
		http_log_err("failed to format remote address");
		http_sock_free(ss);
		return (NULL);
	}
	else if ((ss->hs_raddr_str = cnp_strdup(abuf)) == NULL) {
		http_log_errno("failed to copy remote address");
		http_sock_free(ss);
		return (NULL);
	}

	http_log_info("accepted HTTP connection from %s", abuf);

	return (ss);
}

/*
 * http_sock_getref()
 *
 * Returns a reference to the socket <sock>.
 */
http_sock_t*
http_sock_getref(http_sock_t *sock)
{
	if (cnp_mutex_lock(&sock->hs_lock) == -1) {
		http_log_err("failed to lock HTTP socket");
		return (NULL);
	}

	sock->hs_refcnt++;

	(void)cnp_mutex_unlock(&sock->hs_lock);

	return (sock);
}

/*
 * http_sock_close()
 *
 * Close the HTTP socket <sock>.  No further messages may be sent or received
 * on that socket after it has been closed.
 *
 * Returns 0 on success, or -1 on failure.
 */
int
http_sock_close(http_sock_t *sock)
{
	int refcnt;

	if (cnp_mutex_lock(&sock->hs_lock) == -1) {
		http_log_err("failed to lock HTTP socket");
		return (-1);
	}

	/* release this reference */
	refcnt = --sock->hs_refcnt;

	if (sock->hs_state != HTTP_SOCK_CLOSED) {
		if (sock->hs_fd >= 0) {
			http_log_trace("close(fd=%d)", sock->hs_fd);
			(void)close(sock->hs_fd);
			sock->hs_fd = -1;
		}

		http_log_debug("closing HTTP socket");
		http_sock_setstate(sock, HTTP_SOCK_CLOSED);
	}

	(void)cnp_mutex_unlock(&sock->hs_lock);

	/* no more references, we can free the socket */
	if (refcnt == 0)
		http_sock_free(sock);

	return (0);
}

/*
 * http_sock_alloc()
 *
 * Allocate a new HTTP connection structure and return it.
 */
static http_sock_t*
http_sock_alloc(enum http_sock_type type, int fd)
{
	http_sock_t *sock;

	if ((sock = cnp_malloc(sizeof(*sock))) == NULL) {
		http_log_errno("failed to allocate HTTP socket");
		return (NULL);
	}
	memset(sock, 0, sizeof(*sock));

	sock->hs_type = type;
	sock->hs_fd = fd;
	sock->hs_refcnt = 1;
	sock->hs_state = HTTP_SOCK_INITIALIZED;

	sock->hs_rbuflen = HTTP_SOCK_INITBUFSIZE;
	if ((sock->hs_rbuf = cnp_malloc(sock->hs_rbuflen)) == NULL) {
		http_log_errno("failed to allocate socket read buffer");
		http_sock_free(sock);
		return (NULL);
	}

	if ((sock->hs_wbuf = cnp_buf_alloc(4096, CNP_BUF_AUTOEXT)) == NULL) {
		http_log_errno("failed to allocate socket write buffer");
		http_sock_free(sock);
		return (NULL);
	}

	if (cnp_mutex_init(&sock->hs_lock, NULL) == -1) {
		http_sock_free(sock);
		return (NULL);
	}

	return (sock);
}

/*
 * http_sock_free()
 *
 * Free a reference to the HTTP socket <sock>.  If there are no more references
 * to the socket, all of its associated resources are freed.
 */
static void
http_sock_free(http_sock_t *sock)
{
	if (sock->hs_fd >= 0) {
		http_log_trace("close(fd=%d)", sock->hs_fd);
		(void)close(sock->hs_fd);
	}

	if (sock->hs_session != NULL)
		http_session_free(sock->hs_session);

	if (sock->hs_rbuf != NULL)
		cnp_free(sock->hs_rbuf);

	if (sock->hs_wbuf != NULL)
		cnp_buf_free(sock->hs_wbuf);

	cnp_mutex_destroy(&sock->hs_lock);
	cnp_free(sock);
}

/*
 * http_sock_setflag()
 *
 */
int
http_sock_setflag(http_sock_t *sock, int flag)
{
	if (cnp_mutex_lock(&sock->hs_lock) == -1) {
		http_log_err("failed to lock HTTP socket");
		return (-1);
	}

	sock->hs_flags |= flag;

	if (cnp_mutex_unlock(&sock->hs_lock) == -1)
		http_log_err("failed to unlock HTTP socket");

	return (0);
}

/*
 * http_sock_getflag()
 *
 */
int
http_sock_getflag(http_sock_t *sock, int flag)
{
	return (sock->hs_flags & flag);
}

/*
 * http_sock_getfd()
 *
 * Returns the file descriptor associated with the socket <sock>.
 */
int
http_sock_getfd(http_sock_t *sock)
{
	return (sock->hs_fd);
}

/*
 * http_sock_getsession()
 *
 * Returns the HTTP session associated with the socket <sock>.
 * This function will always return NULL on a socket of type HTTP_SOCK_LISTEN.
 */
http_session_t*
http_sock_getsession(http_sock_t *sock)
{
	return (sock->hs_session);
}

/*
 * http_sock_getladdr()
 *
 * Copy the local address of the socket <sock> into the destination buffer
 * <addr>.
 *
 * Returns 0 on success, or -1 on failure.
 */
int
http_sock_getladdr(http_sock_t *sock, http_sockaddr_t *addr)
{
	if (cnp_mutex_lock(&sock->hs_lock) == -1) {
		http_log_err("failed to lock socket");
		return (-1);
	}
	memcpy(addr, &sock->hs_laddr, sizeof(*addr));

	(void)cnp_mutex_unlock(&sock->hs_lock);
	return (0);
}

/*
 * http_sock_getraddr()
 *
 * Copy the remote address of the socket <sock> into the destination buffer
 * <addr>.
 *
 * Returns 0 on success, or -1 on failure.
 */
int
http_sock_getraddr(http_sock_t *sock, http_sockaddr_t *addr)
{
	if (cnp_mutex_lock(&sock->hs_lock) == -1) {
		http_log_err("failed to lock socket");
		return (-1);
	}
	memcpy(addr, &sock->hs_raddr, sizeof(*addr));

	(void)cnp_mutex_unlock(&sock->hs_lock);
	return (0);
}

/*
 * http_sock_getlport()
 *
 * Returns the local port associated with the socket <sock>.
 */
int
http_sock_getlport(http_sock_t *sock)
{
	return http_addr_getport(&sock->hs_laddr);
}

/*
 * http_sock_getrport()
 *
 * Returns the remote port associated with the socket <sock> if the socket
 * is connected, or -1 otherwise.
 */
int
http_sock_getrport(http_sock_t *sock)
{
	int port = -1;

	if (sock->hs_type == HTTP_SOCK_CONN)
		port = http_addr_getport(&sock->hs_laddr);

	return (port);
}

/*
 * http_sock_getstate()
 *
 * Returns the socket <sock>'s state.
 */
enum http_sock_state
http_sock_getstate(http_sock_t *sock)
{
	return (sock->hs_state);
}

/*
 * http_sock_gettype()
 *
 * Returns the socket <sock>'s type.
 */
enum http_sock_type
http_sock_gettype(http_sock_t *sock)
{
	return (sock->hs_type);
}

/*
 * http_sock_recv()
 *
 * Receive up to <len> bytes of data from the HTTP socket <sock> and store
 * them in the destination buffer <dst>, which is of size <len>.
 *
 * Returns the number of bytes actually received, or -1 on failure.  A return
 * value of 0 indicates that the other end has closed the socket.
 */
ssize_t
http_sock_recv(http_sock_t *sock, void *dst, size_t len)
{
	ssize_t ret;

	if (cnp_mutex_lock(&sock->hs_lock) == -1) {
		http_log_err("failed to lock HTTP socket");
		ret = -1;
	}
	else {
		ret = http_sock_recv_nolock(sock, dst, len);

		if (cnp_mutex_unlock(&sock->hs_lock) == -1)
			http_log_err("failed to unlock HTTP socket");
	}

	return (ret);
}

/*
 * http_sock_recv_nolock()
 *
 * Receive up to <len> bytes of data from the HTTP socket <sock> and store
 * them in the destination buffer <dst>.
 *
 * Returns the number of bytes actually received, or -1 on failure.  A return
 * value of 0 indicates that the other end has closed the socket.
 */
static ssize_t
http_sock_recv_nolock(http_sock_t *sock, void *dst, size_t len)
{
	int fl = 0;
	ssize_t ret;
	size_t left = len;
	uint8_t *bp = (uint8_t *)dst;

#ifdef MSG_NOSIGNAL
	fl |= MSG_NOSIGNAL;
#endif

	do {
		http_log_trace("recv(fd=%d, buf=%p, len=%zu, flags=0x%08x)",
		    sock->hs_fd, bp, left, fl);
		if ((ret = recv(sock->hs_fd, bp, left, fl)) == 0) {
			http_log_info("connection closed");
			http_sock_setstate(sock, HTTP_SOCK_CLOSED);
		}
		else if (ret == -1) {
			if (errno == EAGAIN)
				continue;
			http_log_errno("receive error");
			break;
		}
	} while (0);

	if (ret > 0)
		http_log_debug("%zd bytes received from socket", ret);

	return (ret);
}

/*
 * http_sock_recvmsg()
 *
 * Receive an HTTP message from the socket <sock> and store its contents in the
 * message structure <msg>.
 *
 * Returns the HTTP request on success, or NULL on failure.
 */
ssize_t
http_sock_recvmsg(http_sock_t *sock, http_msg_t *msg, int flags)
{
	ssize_t ret, total;
	size_t left, off;
	uint8_t *bp;

	if (cnp_mutex_lock(&sock->hs_lock) == -1) {
		http_log_err("failed to lock HTTP socket");
		return (-1);
	}

	bp = (uint8_t *)sock->hs_rbuf;
	left = sock->hs_rbuflen;
	total = off = 0;

	http_log_debug("receiving HTTP message from %s",
	    sock->hs_raddr_str);

	do {
		if ((ret = http_sock_recv_nolock(sock, bp, left)) <= 0)
			break;

		bp += (size_t)ret;
		left -= (size_t)ret;
		total += (size_t)ret;

		/*
		 * Look for the 2 consecutive CRLFs that mark the end of the
		 * request headers.  If we find only one, this is the end
		 * of a single header, and we update the offset to start from
		 * there on the next lookup...
		 */
		ret = http_sock_findeoh(sock->hs_rbuf, total, &off);
		if (ret != -1)
			break;

		if (left == 0) {
			http_sock_growrbuf(sock, sock->hs_rbuflen + 1024);
			bp = ((uint8_t *)sock->hs_rbuf) + total;
			left = 1024;
		}
	} while (1);


	if (total > 0) {
		*bp = '\0';
		http_msg_parse(msg, sock->hs_rbuf, total);
		time(&msg->msg_crtime);
		msg->msg_req.req_sock = sock;
	}
	else if (ret == -1)
		total = -1;

	if (cnp_mutex_unlock(&sock->hs_lock) == -1)
		http_log_err("failed to unlock HTTP socket");

	return (total);
}

/*
 * http_sock_send()
 *
 * Send the first <len> bytes of data from the source buffer <src> on the
 * HTTP socket <sock>.
 *
 * Returns 0 on success, or -1 on failure.
 */
ssize_t
http_sock_send(http_sock_t *sock, const void *src, size_t len)
{
	ssize_t ret = -1;

	if (len == 0)
		return (0);

	if (cnp_mutex_lock(&sock->hs_lock) == -1) {
		http_log_err("failed to lock HTTP socket");
	}
	else {
		ret = http_sock_send_nolock(sock, src, len);

		if (cnp_mutex_unlock(&sock->hs_lock) == -1)
			http_log_err("failed to unlock HTTP socket");
	}

	return (ret);
}

/*
 * http_sock_send_nolock()
 *
 * Send the first <len> bytes of data from the source buffer <src> on the
 * HTTP socket <sock>.
 *
 * Returns 0 on success, or -1 on failure.
 */
static ssize_t
http_sock_send_nolock(http_sock_t *sock, const void *src, size_t len)
{
	int fl = 0;
	ssize_t ret;
	size_t left;
	const uint8_t *bp;

#ifdef MSG_NOSIGNAL
	fl |= MSG_NOSIGNAL;
#endif

	left = len;
	bp = (const uint8_t *)src;

	do {
		http_log_trace("send(fd=%d, buf=%p, len=%zu, flags=0x%08x)",
		    sock->hs_fd, bp, left, fl);
		ret = send(sock->hs_fd, bp, left, fl);
		if (ret == -1) {
			if (errno == EINTR)
				continue;

			if ((errno == EPIPE) || (errno == ECONNRESET)) {
				/* connection closed remotely */
				http_sock_setstate(sock, HTTP_SOCK_CLOSED);
				break;
			}

			http_log_debug("send error");
		}

		bp += (size_t)ret;
		left -= (size_t)ret;

	} while (left > 0);

	return (ssize_t)(len - left);
}

/*
 * http_sock_sendmsg()
 *
 * Send the HTTP message <msg> to the other endpoint of the
 * socket <sock>.  The argument <flags> controls which portions of the
 * message are sent and how.
 * Returns 0 on success, or -1 on failure.
 */
ssize_t
http_sock_sendmsg(http_sock_t *sock, http_msg_t *msg, int flags)
{
	int fd;
	size_t len;
	ssize_t ret;
	struct stat st;

	if (cnp_mutex_lock(&sock->hs_lock) == -1) {
		http_log_err("failed to lock HTTP socket");
		return (-1);
	}

	ret = http_msg_formatbuf(msg, sock->hs_wbuf, HTTP_MSG_HDR);
	if (ret == -1) {
		(void)cnp_mutex_unlock(&sock->hs_lock);
		return (-1);
	}

	http_log_debug("sending %zd bytes of HTTP message", ret);

	ret = http_sock_send_nolock(sock, cnp_buf_peek(sock->hs_wbuf, 0),
	    (size_t)ret);
	if (ret == -1) {
		(void)cnp_mutex_unlock(&sock->hs_lock);
		return (-1);
	}

	if (msg->msg_entity != NULL) {
		http_log_debug("sending entity");
		if (msg->msg_entity->ent_flags & HTTP_ENTITY_VOLATILE) {
			http_sock_send_nolock(sock, msg->msg_entity->ent_body,
			    (size_t)msg->msg_entity->ent_size);
		}
		else {
			http_log_trace("open(path=%s, flags=0x%08x)",
			    msg->msg_entity->ent_path, O_RDONLY);
			fd = open(msg->msg_entity->ent_path, O_RDONLY);
			if (fd == -1) {
				http_log_errno("failed to open %s",
				    msg->msg_entity->ent_path);
			}
			else {
				fstat(fd, &st);

				len = (size_t)st.st_size;

				http_sock_sendfile_nolock(sock, fd,
				    (off_t)0, &len, 0);
				(void)close(fd);
			}
		}

		http_log_debug("%zd bytes of message body sent", ret);
	}

	if (cnp_mutex_unlock(&sock->hs_lock) == -1)
		http_log_err("failed to unlock HTTP socket");

	return (0);
}

/*
 * http_sock_sendfile()
 *
 * Read the first <len> bytes of data from the file descriptor <fd> starting at
 * file offset <off> and send them over the HTTP socket <sock>.
 *
 * Returns 0 on success, or -1 on failure.
 */
int
http_sock_sendfile(http_sock_t *sock, int fd, off_t off, size_t *len, int flags)
{
	ssize_t ret = -1;

	if (cnp_mutex_lock(&sock->hs_lock) == -1) {
		http_log_err("failed to lock HTTP socket");
	}
	else {
		ret = http_sock_sendfile_nolock(sock, fd, off, len, flags);

		if (cnp_mutex_unlock(&sock->hs_lock) == -1)
			http_log_err("failed to unlock HTTP socket");
	}

	return (ret);
}

/*
 * http_sock_sendfile_nolock()
 *
 * Read the first <len> bytes of data from the file descriptor <fd> and send
 * them over the HTTP socket <sock>.
 * Returns 0 on success, or -1 on failure.
 */
static int
http_sock_sendfile_nolock(http_sock_t *sock, int fd, off_t offset, size_t *len,
    int flags)
{
	int ret = 0;
	off_t off = offset;
	size_t total;
	ssize_t count;
#if defined(__FreeBSD__)
	off_t sbytes;
#elif !defined(__linux__)
	char buf[4096];

	http_log_trace("lseek(fd=%d, offset=%lld, whence=%d)",
	    fd, off, SEEK_SET);
	if (lseek(fd, off, SEEK_SET) == -1) {
		http_log_errno("failed to set file offset");
		return (-1);
	}
#endif

	for (total = 0; total < *len; total += (size_t)count) {
#if defined(__linux__)
		count = sendfile(sock->hs_fd, fd, &off, *len - total);
		if (count == -1) {
			if (errno == EAGAIN) {
				count = 0;
				continue;
			}
			http_log_errno("sendfile failed");
			break;
		}
#elif defined(__FreeBSD__)
		if (sendfile(sock->hs_fd, fd, off, *len - total, NULL,
		    &sbytes, 0) == -1) {
			if ((errno == EINTR) || (errno == EAGAIN)) {
				count = 0;
				continue;
			}
			http_log_errno("sendfile failed");
			break;

		}
		count = (ssize_t)sbytes;
#else
		http_log_trace("read(fd=%d, buf=%p, nbytes=%lu)",
		    fd, buf, MIN((*len - total), sizeof(buf)));
		count = read(fd, buf, MIN((*len - total), sizeof(buf)));
		if (count == -1)
			break;
		else if (count == -1) {
			if (errno == EINTR)
				continue;

			http_log_errno("failed to read from fd %d", fd);
			break;
		}

		count = http_sock_send_nolock(sock, buf, (size_t)count);
		if (count == -1) {
			http_log_err("failed to send file");
			break;
		}
#endif
	}

	if (total < *len)
		ret = -1;
	*len = total;

	return (ret);
}

/*
 * http_sock_findeoh()
 *
 * Look for the end of the headers portion of an HTTP message in the buffer
 * <buf>, starting at the offset pointed to by <off>.
 *
 * Returns the total length of the headers portion on success, -1 otherwise.
 */
static ssize_t
http_sock_findeoh(const uint8_t *buf, size_t len, size_t *off)
{
	size_t i;
	ssize_t ret = -1;

	for (i = *off; i < len - 3; i++) {
		if ((buf[i] == '\r') && (buf[i + 1] == '\n') &&
		    (buf[i + 2] == '\r') && (buf[i + 3] == '\n')) {
			ret = i + 4;
			break;
		}
	}

	if (ret == -1)
		*off = i;

	return (ret);
}

/*
 * http_sock_setstate()
 *
 */
static void
http_sock_setstate(http_sock_t *sock, enum http_sock_state state)
{
	http_log_debug("socket state change: %s => %s",
	    http_sock_state_names[sock->hs_state],
	    http_sock_state_names[state]);
	sock->hs_state = state;
}

/*
 * http_sock_growrbuf()
 *
 */
static int
http_sock_growrbuf(http_sock_t *sock, size_t newsize)
{
	void *tmp;

	if ((tmp = cnp_realloc(sock->hs_rbuf, newsize)) == NULL) {
		http_log_errno("failed to grow socket read buffer");
		return (-1);
	}

	sock->hs_rbuf = tmp;
	sock->hs_rbuflen = newsize;
	return (0);
}

